# 利用adb实现屏控
"""
run_adb_command：用于执行ADB命令并捕获输出。
get_ui_tree：通过uiautomator dump获取当前界面的UI树，并将其保存为本地的window_dump.xml文件。
parse_ui_tree：解析window_dump.xml文件，查找目标元素的resource-id，并计算其中心坐标。
click_element：通过ADB命令点击指定坐标。
main：主函数，定义目标元素的resource-id，调用上述函数完成操作点击。
"""
import logging
import subprocess
import xml.etree.ElementTree as element_tree
import re
from typing import Tuple

class ADBScreenControllerLibrary:
    def __init__(self):
        # 初始化日志配置
        logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
        logging.info("ADB Screen Controller Library 初始化完成。")

    @staticmethod
    def run_adb_command(command):
        """
        运行ADB命令并返回输出。

        :param command: 要执行的ADB命令
        :return: 命令的标准输出
        :raises: 如果命令执行失败，抛出异常并记录日志
        """
        logging.info(f"正在执行ADB命令: {command}")
        try:
            result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            if result.returncode != 0:
                logging.error(f"ADB命令执行失败: {result.stderr}")
                raise Exception(f"ADB命令执行失败: {result.stderr}")
            logging.info(f"ADB命令执行成功，输出: {result.stdout}")
            return result.stdout
        except Exception as e:
            logging.error(f"运行ADB命令时发生异常: {e}")
            raise

    def get_ui_tree(self, ui_tree_file="window_dump.xml"):
        """
        获取当前界面的UI树并保存为XML文件。

        :param ui_tree_file: 保存UI树的本地文件名，默认为 'window_dump.xml'
        :raises: 如果命令执行失败或文件保存失败，抛出异常并记录日志
        """
        logging.info(f"开始获取UI树并保存到文件: {ui_tree_file}")
        try:
            self.run_adb_command("adb shell uiautomator dump /sdcard/window_dump.xml")
            self.run_adb_command(f"adb pull /sdcard/window_dump.xml {ui_tree_file}")
            logging.info(f"UI树已成功保存到本地文件：{ui_tree_file}")
        except Exception as e:
            logging.error(f"获取UI树失败: {e}")
            raise

    @staticmethod
    def parse_ui_tree(ui_tree_file, resource_id):
        """
        解析UI树XML文件，查找目标元素的坐标。

        :param ui_tree_file: 保存UI树的本地文件名
        :param resource_id: 要查找的元素的resource-id
        :return: 目标元素的中心坐标 (x, y)
        :raises: 如果文件未找到、解析失败或未找到目标元素，抛出异常并记录日志
        """
        logging.info(f"开始解析UI树文件: {ui_tree_file}，查找resource-id: {resource_id}")
        try:
            tree = element_tree.parse(ui_tree_file)
            root = tree.getroot()
        except FileNotFoundError:
            logging.error(f"UI树文件 {ui_tree_file} 未找到，请先运行 'Get UI Tree' 关键字。")
            raise Exception(f"UI树文件 {ui_tree_file} 未找到，请先运行 'Get UI Tree' 关键字。")
        except element_tree.ParseError:
            logging.error("UI树文件解析失败，可能是文件损坏或格式不正确。")
            raise Exception("UI树文件解析失败，可能是文件损坏或格式不正确。")

        # 查找目标元素
        for node in root.iter("node"):
            if node.attrib.get("resource-id") == resource_id:
                bounds = node.attrib.get("bounds")
                if bounds:
                    # 使用正则表达式提取坐标
                    match = re.search(r"\[(\d+),(\d+)\]\[(\d+),(\d+)\]", bounds)
                    if match:
                        x1, y1, x2, y2 = map(int, match.groups())
                        x = (x1 + x2) // 2
                        y = (y1 + y2) // 2
                        logging.info(f"找到目标元素，resource-id: {resource_id}，中心坐标: ({x}, {y})")
                        return x, y
        logging.error(f"未找到resource-id为'{resource_id}'的元素。")
        raise Exception(f"未找到resource-id为'{resource_id}'的元素。")

    def click_element(self, x, y):
        """
        通过ADB命令点击指定坐标。

        :param x: 要点击的坐标的x值
        :param y: 要点击的坐标的y值
        :raises: 如果点击命令执行失败，抛出异常并记录日志
        """
        logging.info(f"正在点击坐标: ({x}, {y})")
        try:
            self.run_adb_command(f"adb shell input tap {x} {y}")
            logging.info(f"已成功点击坐标：({x}, {y})")
        except Exception as e:
            logging.error(f"点击坐标失败: {e}")
            raise

    def swipe_screen(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 200):
        """
        执行滑屏操作。

        :param start_x: 起始X坐标
        :param start_y: 起始Y坐标
        :param end_x: 结束X坐标
        :param end_y: 结束Y坐标
        :param duration: 滑动持续时间（毫秒，默认200ms）
        """
        logging.info(f"开始滑屏操作：从 ({start_x}, {start_y}) 到 ({end_x}, {end_y})，持续时间 {duration}ms")
        try:
            command = f"adb shell input swipe {start_x} {start_y} {end_x} {end_y} {duration}"
            self.run_adb_command(command)
            logging.info("滑屏操作成功完成。")
        except Exception as e:
            logging.error(f"滑屏操作失败: {e}")
            raise

    @staticmethod
    def call_number(number):
        """
        使用ADB命令拨打指定号码。
        参数:
            number (str): 要拨打的电话号码
        """
        try:
            # 构造ADB命令
            adb_command = f"adb shell am start -a android.intent.action.CALL -d tel:{number}"
            # 执行ADB命令
            subprocess.run(adb_command, shell=True, check=True)
            print(f"正在拨打号码: {number}")
        except subprocess.CalledProcessError as e:
            print(f"拨打电话失败: {e}")

    # 调用函数拨打10086
    call_number("10086")

    def reboot_device(self, mode="normal"):
        """
        重启设备到指定模式。
        :param mode: 重启模式，可选值为 'normal'、'bootloader' 或 'recovery'
        """
        if mode == "bootloader":
            command = "adb reboot bootloader"
        elif mode == "recovery":
            command = "adb reboot recovery"
        else:
            command = "adb reboot"

        output = self.run_adb_command(command)
        return output
